/**
 * @Title wheel-utils系统
 * @Package com.senyint.sys.utils.spring
 * @Description TODO
 * @Company 心医国际数字医疗系统（大连） 有限公司
 * @Copyright (c) 2016, 2017, Senyint. All rights reserved.
 * @date 2018年3月7日 下午1:32:29
 * @author WangNaiFei
 * @version 1.0
 */

package com.senyint.sys.utils.spring.groovy;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;

import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.core.io.FileSystemResource;
import org.springframework.scripting.ScriptCompilationException;
import org.springframework.scripting.ScriptSource;
import org.springframework.scripting.groovy.GroovyScriptFactory;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.scripting.support.ScriptFactoryPostProcessor;

import com.senyint.sys.utils.spring.SpringUtils;

/**
 * {@code GroovyUtils}
 * 
 * @Description: groovy工具
 * <p>
 * @author WangNaiFei,WangNaiFei@senyint.com
 * @date 2018年3月7日 下午1:32:29
 * @version 1.0
 * @since jdk1.7+
 */
public class GroovyUtils {

	static final String UNCARE_SCRIPT_LOCATOR = "a script source locator (doesn't matter here)";
	static final String GROOVY_FACTORY_NAME = "org.springframework.scripting.groovy.GroovyScriptFactory";

	/**
	 * @Title: getGroovyClass
	 * @Description:
	 * @author WangNaiFei,WangNaiFei@senyint.com
	 * @date 2018年3月8日 上午11:10:53
	 * @version 1.0
	 * @param groovySource
	 * @return
	 * @throws ScriptCompilationException
	 * @throws IOException
	 */
	public static Class<?> getGroovyClass(ScriptSource groovySource) throws ScriptCompilationException, IOException {
		GroovyScriptFactory groovyScriptFactory = new GroovyScriptFactory(UNCARE_SCRIPT_LOCATOR);
		return groovyScriptFactory.getScriptedObjectType(groovySource);
	}

	/**
	 * @Title: getScriptedObject
	 * @Description:
	 * @author WangNaiFei,WangNaiFei@senyint.com
	 * @date 2018年3月20日 下午6:43:03
	 * @version 1.0
	 * @param groovySource
	 * @param interfaces
	 * @return
	 * @throws ScriptCompilationException
	 * @throws IOException
	 * @deprecated 源码中实例化不需要接口 使用instance实例化的
	 */
	@Deprecated
	public static Object getScriptedObject(ScriptSource groovySource, Class<?>... interfaces) throws ScriptCompilationException, IOException {
		GroovyScriptFactory groovyScriptFactory = new GroovyScriptFactory(UNCARE_SCRIPT_LOCATOR);
		return groovyScriptFactory.getScriptedObject(groovySource, interfaces);
	}

	/**
	 * @Title: CheckGroovyFile
	 * @Description: 简单判断是否是groovy文件
	 * @author WangNaiFei,WangNaiFei@senyint.com
	 * @date 2018年3月7日 下午2:21:28
	 * @version 1.0
	 * @param groovyFile
	 * @return
	 * @throws FileNotFoundException
	 */
	public static boolean isGroovyFile(File groovyFile) {
		if (!groovyFile.exists() || !groovyFile.isFile() || !groovyFile.getPath().toLowerCase().endsWith(".groovy")) {
			return false;
		}
		return true;
	}

	/**
	 * @Title: registerGroovyBean
	 * @Description: TODO
	 * @author WangNaiFei,WangNaiFei@senyint.com
	 * @date 2018年3月8日 下午2:35:19
	 * @version 1.0
	 * @param groovyFile
	 * @param groovyInterface
	 * @param beanName
	 * @throws ScriptCompilationException
	 * @throws IOException
	 * @see 根据#ScriptSource.isModified()自动重载groovy
	 */
	public static void registerGroovyBean(File groovyFile, Class<?> groovyInterface, String beanName, ConfigurableListableBeanFactory beanFactory) throws ScriptCompilationException, IOException {
		if (GroovyUtils.isGroovyFile(groovyFile)) {
			removeGroovyFileBOM(groovyFile);
			ScriptSource scriptSource = new ResourceScriptSource(new FileSystemResource(groovyFile));
			registerGroovyBean(scriptSource, groovyInterface, beanName, beanFactory);
		} else {
			throw new IllegalArgumentException("==> 脚本文件不是groovy文件，或者不存在，path：" + groovyFile.getPath());
		}
	}

	/**
	 * @Title: registerGroovyBean
	 * @Description: 注册groovy脚本 强制验证接口
	 * @author WangNaiFei,WangNaiFei@senyint.com
	 * @date 2018年3月8日 下午2:34:52
	 * @version 1.0
	 * @param scriptSource
	 * @param groovyInterface
	 * @param beanName
	 * @throws ScriptCompilationException
	 * @throws IOException
	 */
	public static void registerGroovyBean(ScriptSource scriptSource, Class<?> groovyInterface, String beanName, ConfigurableListableBeanFactory beanFactory) throws ScriptCompilationException, IOException {
		Class<?> scriptedObjectType = GroovyUtils.getGroovyClass(scriptSource);
		if (groovyInterface == null || groovyInterface.isAssignableFrom(scriptedObjectType)) {
			// registerBean(scriptedObjectType, beanName, registry); //注册后不调用时不实例化，内部注解不运行
			SpringUtils.registerSingletonBean(beanName, scriptedObjectType, beanFactory);
		} else {
			throw new IllegalArgumentException("==> 脚本未实现其接口或不是其子类:" + groovyInterface.getName());
		}
	}

	/**
	 * @Title: registerGroovyBean
	 * @Description: 获取脚本class对象
	 * @author WangNaiFei,WangNaiFei@senyint.com
	 * @date 2018年3月8日 下午2:35:19
	 * @version 1.0
	 * @param groovyFile
	 * @param groovyInterface
	 * @param beanName
	 * @throws ScriptCompilationException
	 * @throws IOException
	 * @see 根据#ScriptSource.isModified()自动重载groovy
	 */
	public static void registerGroovyBean(File groovyFile, String beanName, ConfigurableListableBeanFactory beanFactory) throws ScriptCompilationException, IOException {
		registerGroovyBean(groovyFile, null, beanName, beanFactory);
	}

	/**
	 * @Title: registerGroovyBean
	 * @Description: 注册groovy脚本
	 * @author WangNaiFei,WangNaiFei@senyint.com
	 * @date 2018年3月8日 下午2:34:52
	 * @version 1.0
	 * @param scriptSource
	 * @param groovyInterface
	 * @param beanName
	 * @throws ScriptCompilationException
	 * @throws IOException
	 */
	public static void registerGroovyBean(ScriptSource scriptSource, String beanName, ConfigurableListableBeanFactory beanFactory) throws ScriptCompilationException, IOException {
		registerGroovyBean(scriptSource, null, beanName, beanFactory);
	}

	/**
	 * @Title: registerGroovyBean
	 * @Description: spring 动态加载脚本标准实现 自动刷新
	 * register {@link ScriptFactoryPostProcessor}
	 * 类似<br>
	 * <code>
	 * &lt;bean id="groovyMessenger" class="org.springframework.scripting.groovy.GroovyScriptFactory"&gt;
	 * &lt;constructor-arg value="classpath:mypackage/Messenger.groovy"/&gt;
	 * &lt;property name="message" value="Hello World!"/&gt;
	 * &lt;/bean&gt;
	 * </code>
	 * @author WangNaiFei,WangNaiFei@senyint.com
	 * @date 2019年2月28日 上午11:10:10
	 * @version 1.0
	 * @param groovyFile
	 * @param delay
	 * @param registry
	 * @see ScriptFactoryPostProcessor
	 * @see 注入的bean没有实例化 部分需要实例化注解不能运行
	 */
	public static void registerGroovyBean(File groovyFile, int delay, BeanDefinitionRegistry registry) {
		//
		removeGroovyFileBOM(groovyFile);
		//
		GenericBeanDefinition bd = new GenericBeanDefinition();
		//
		bd.setBeanClassName(GROOVY_FACTORY_NAME);
		// 刷新时间
		bd.setAttribute(ScriptFactoryPostProcessor.REFRESH_CHECK_DELAY_ATTRIBUTE, delay);
		// 语言脚本
		bd.setAttribute(ScriptFactoryPostProcessor.LANGUAGE_ATTRIBUTE, "groovy");
		// 文件
		bd.getConstructorArgumentValues().addIndexedArgumentValue(0, groovyFile.getPath());
		// 注册到spring容器
		registry.registerBeanDefinition(groovyFile.getPath(), bd);
	}

	/**
	 * @Title: removeGroovyFileBOM
	 * @Description: 删除windows文本编辑器的BOM
	 * @author WangNaiFei,WangNaiFei@senyint.com
	 * @date 2019年2月28日 下午3:05:51
	 * @version 1.0
	 * @param oldFile
	 */
	public static void removeGroovyFileBOM(File oldFile) {
		if (!oldFile.exists())
			return;
		if (oldFile.length() <= 3)
			return;

		try (RandomAccessFile raf = new RandomAccessFile(oldFile, "r")) {
			byte readByte = (byte) raf.read();

			if (readByte != (byte) 0xef)
				return; // not include BOM

		} catch (FileNotFoundException e) {

		} catch (IOException e) {

		}

		String newFilePath = oldFile.getPath() + ".new";
		FileOutputStream fos = null;
		FileInputStream fis = null;

		try {
			fis = new FileInputStream(oldFile);
			fos = new FileOutputStream(newFilePath);
			for (int i = 0; i < 3; ++i)// 跳过BOM
				fis.read();

			while (true) {
				int readChar = fis.read();
				if (readChar == -1)
					break;

				fos.write(readChar);
			}

		} catch (FileNotFoundException e) {

		} catch (IOException e) {

		} finally {
			if (fis != null)
				try {
					fis.close();
				} catch (IOException e) {
				}
			if (fos != null)
				try {
					fos.close();
				} catch (IOException e) {
				}
		}

		if (oldFile.delete()) {
			File newFile = new File(newFilePath);
			newFile.renameTo(oldFile);
		}

	}
}
